
package com.futurepress.staticserver;

import android.content.res.AssetManager;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.LifecycleEventListener;

import java.io.*;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import java.net.ServerSocket;

import android.util.Log;


import fi.iki.elonen.SimpleWebServer;

public class FPStaticServerModule extends ReactContextBaseJavaModule implements LifecycleEventListener {

  private final ReactApplicationContext reactContext;

  private static final String LOGTAG = "FPStaticServerModule";

  private File www_root = null;
  private int port = 9999;
  private boolean localhost_only = false;
  private boolean keep_alive = false;

  private String localPath = "";
  private SimpleWebServer server = null;
  private String	url = "";

  private String rootPath = "";

  public FPStaticServerModule(ReactApplicationContext reactContext) {
    super(reactContext);
    this.reactContext = reactContext;
  }

  private String __getLocalIpAddress() {
    try {
      for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) {
        NetworkInterface intf = en.nextElement();
        for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) {
          InetAddress inetAddress = enumIpAddr.nextElement();
          if (! inetAddress.isLoopbackAddress()) {
            String ip = inetAddress.getHostAddress();
            if(InetAddressUtils.isIPv4Address(ip)) {
              Log.w(LOGTAG, "local IP: "+ ip);
              return ip;
            }
          }
        }
      }
    } catch (SocketException ex) {
      Log.e(LOGTAG, ex.toString());
    }

    return "127.0.0.1";
  }

  @Override
  public String getName() {
    return "FPStaticServer";
  }

  @ReactMethod
  public String getLocalPath() {
    return localPath;
  }

  @ReactMethod
  public void start(String _port, String root, Boolean localhost, Boolean keepAlive, Promise promise) {

    if (server != null){
      promise.resolve(url);
      return;
    }

    if (_port != null) {
      try {
        port = Integer.parseInt(_port);

        if (port == 0) {
          try {
            port = this.findRandomOpenPort();
          } catch (IOException e) {
            port = 9999;
          }
        }
      } catch(NumberFormatException nfe) {
        try {
          port = this.findRandomOpenPort();
        } catch (IOException e) {
          port = 9999;
        }
      }
    }

    rootPath = this.reactContext.getCacheDir().getPath();

    if (root != null) {
      if (root.startsWith("/")) {
        www_root = new File(root);
        localPath = www_root.getAbsolutePath();
      } else if (root.startsWith("file:///android_asset")) {
        String folder = root.replace("file:///android_asset/", "");
        rootPath = rootPath + "/" + folder;

        if (!checkFileOrDicExist(rootPath)) {
          copyAssetsToCache(folder);
        }

        www_root = new File(rootPath);
        localPath = www_root.getAbsolutePath();
      }
    } else {
      www_root = new File(this.reactContext.getFilesDir(), "");
      localPath = www_root.getAbsolutePath();
    }

    if (localhost != null) {
      localhost_only = localhost;
    }

    if (keepAlive != null) {
      keep_alive = keepAlive;
    }

    try {

      if(localhost_only) {
        server = new WebServer("localhost", port, www_root);
      } else {
        server = new WebServer(__getLocalIpAddress(), port, www_root);
      }


      if (localhost_only) {
        url = "http://localhost:" + port;
      } else {
        url = "http://" + __getLocalIpAddress() + ":" + port;
      }

      server.start();

      promise.resolve(url);

    } catch (IOException e) {
      String msg = e.getMessage();

      // Server doesn't stop on refresh
      if (server != null && msg.equals("bind failed: EADDRINUSE (Address already in use)")){
        promise.resolve(url);
      } else {
        promise.reject(null, msg);
      }
    }
  }

  private Integer findRandomOpenPort() throws IOException {
    try {
      ServerSocket socket = new ServerSocket(0);
      int port = socket.getLocalPort();
      Log.w(LOGTAG, "port:" + port);
      socket.close();
      return port;
    } catch (IOException e) {
      return 0;
    }
  }

  @ReactMethod
  public void stop() {
    if (server != null) {
      Log.w(LOGTAG, "Stopped Server");
      server.stop();
      server = null;
    }
  }

  @ReactMethod
  public void origin(Promise promise) {
    if (server != null) {
      promise.resolve(url);
    } else {
      promise.resolve("");
    }
  }

  @ReactMethod
  public void isRunning(Promise promise) {
    promise.resolve(server != null && server.isAlive());
  }

  /* Shut down the server if app is destroyed or paused */
  @Override
  public void onHostResume() {
    //start(null, null, null, null);
  }

  @Override
  public void onHostPause() {
    //stop();
  }

  @Override
  public void onHostDestroy() {
    stop();
  }

  public boolean checkFileOrDicExist(String targetPath) {
    return (isFileExist(targetPath) || isDirectoryExist(targetPath));
  }

  public static boolean isFileExist(String filePath) {
    if (filePath == null || filePath.isEmpty()) return false;
    File file = new File(filePath);
    return file.exists() && file.isFile();
  }

  public static boolean isDirectoryExist(String dirPath) {
    if (dirPath == null || dirPath.isEmpty()) return false;
    File dir = new File(dirPath);
    return dir.exists() && dir.isDirectory();
  }

  public void copyAssetsToCache(String assetFolder) {
    AssetManager assetManager = this.reactContext.getAssets();
    File cacheDir = this.reactContext.getCacheDir();

    try {
      String[] files = assetManager.list(assetFolder);
      if (files == null || files.length == 0) {
        copyFile(assetManager, assetFolder, cacheDir);
      } else {
        for (String file : files) {
          String subPath = assetFolder.isEmpty() ? file : assetFolder + File.separator + file;
          copyAssetsToCache(subPath);
        }
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  private void copyFile(AssetManager assetManager, String assetPath, File targetDir) {
    InputStream inputStream = null;
    OutputStream outputStream = null;

    try {
      inputStream = assetManager.open(assetPath);
      File targetFile = new File(targetDir, assetPath);

      File parentDir = targetFile.getParentFile();
      if (parentDir != null && !parentDir.exists()) {
        parentDir.mkdirs();
      }

      outputStream = new FileOutputStream(targetFile);

      byte[] buffer = new byte[1024];
      int length;
      while ((length = inputStream.read(buffer)) > 0) {
        outputStream.write(buffer, 0, length);
      }

      outputStream.flush();
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      try {
        if (inputStream != null) inputStream.close();
        if (outputStream != null) outputStream.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

}
